<!DOCTYPE html>
<!--
Copyright (c) 2015 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/core/auditor.html">

<script>
'use strict';

function filterDuplicateTimestamps(timestamps) {
  const dedupedTimestamps = [];
  let lastTs = 0;
  for (const ts of timestamps) {
    if (ts - lastTs >= 1) {
      dedupedTimestamps.push(ts);
      lastTs = ts;
    }
  }
  return dedupedTimestamps;
}

tr.exportTo('tr.e.audits', function() {
  const VSYNC_COUNTER_PRECISIONS = {
    // Android. Some versions have VSYNC split out into VSYNC-app and VSYNC-sf.
    // Requires "gfx" systrace category to be enabled.
    'android.VSYNC-app': 15,
    'android.VSYNC': 15
  };

  const VSYNC_SLICE_PRECISIONS = {
    // Android.
    'RenderWidgetHostViewAndroid::OnVSync': 5,
    // Android. Very precise. Requires "gfx" systrace category to be enabled.
    'VSYNC': 10,
    // Linux. Very precise. Requires "gpu" tracing category to be enabled.
    'vblank': 10,
    // Mac. Derived from a Mac callback (CVDisplayLinkSetOutputCallback).
    'DisplayLinkMac::GetVSyncParameters': 5
  };

  const BEGIN_FRAME_SLICE_PRECISION = {
    'DisplayScheduler::BeginFrame': 10
  };

  /**
   * Auditor that analyzes the model and, if possible, adds data to it
   * indicating when vertical sync events took place.
   *
   * @constructor
   * @extends {tr.c.Auditor}
   */
  function VSyncAuditor(model) {
    tr.c.Auditor.call(this, model);
  }

  VSyncAuditor.prototype = {
    __proto__: tr.c.Auditor.prototype,

    runAnnotate() {
      this.model.device.vSyncTimestamps = this.findVSyncTimestamps(this.model);
    },

    /**
     * Returns an array of the most accurate VSync times available in the model.
     */
    findVSyncTimestamps(model) {
      let times = [];

      // Only keep the most precise VSync data.
      let maxPrecision = Number.NEGATIVE_INFINITY;
      let maxTitle = undefined;

      function useInstead(title, precisions) {
        const precision = precisions[title];
        if (precision === undefined) return false;

        if (title === maxTitle) return true;

        if (precision <= maxPrecision) {
          if (precision === maxPrecision) {
            model.importWarning({
              type: 'VSyncAuditor',
              message: 'Encountered two different VSync events (' +
                  maxTitle + ', ' + title + ') with the same precision, ' +
                  'ignoring the newer one (' + title + ')',
              showToUser: false,
            });
          }
          return false;
        }
        maxPrecision = precision;
        maxTitle = title;
        times = [];

        return true;
      }

      for (const pid in model.processes) {
        const process = model.processes[pid];
        // Traverse process counters.
        for (const cid in process.counters) {
          if (useInstead(cid, VSYNC_COUNTER_PRECISIONS)) {
            const counter = process.counters[cid];
            for (let i = 0; i < counter.series.length; i++) {
              const series = counter.series[i];
              Array.prototype.push.apply(times, series.timestamps);
            }
          }
        }

        // Traverse thread slices.
        for (const tid in process.threads) {
          const thread = process.threads[tid];
          for (let i = 0; i < thread.sliceGroup.slices.length; i++) {
            const slice = thread.sliceGroup.slices[i];
            if (useInstead(slice.title, VSYNC_SLICE_PRECISIONS)) {
              times.push(slice.start);
            } else if (useInstead(slice.title, BEGIN_FRAME_SLICE_PRECISION) &&
                       slice.args.args && slice.args.args.frame_time_us) {
              // We need to check not only that we have a Scheduler::BeginFrame
              // event, but also that we have one that has a frame time
              // associated with it.
              // Older versions of Scheduler::BeginFrame don't have one.
              times.push(slice.args.args.frame_time_us / 1000.0);
            }
          }
        }
      }
      times.sort(function(x, y) { return x - y; });
      return filterDuplicateTimestamps(times);
    }
  };

  tr.c.Auditor.register(VSyncAuditor);

  return {
    VSyncAuditor,
  };
});
</script>
